// physics.cpp : contains the definition of the physics component, 
// which is responsible for all physics collisions between objects
//$Id: Physics.cpp 46 2009-09-24 22:38:34Z brwarner2 $

#include "stdafx.h"
#include "Physics.h"
#include "Area.h"
#include "Messages.h"
#include "ObjectManager.h"
#include "Render.h"
#include "Entity.h"
#include "GameMonkey.h"

CPhysics::CPhysics(void)
{
	//Set size information
	m_width = m_height = m_depth = std::numeric_limits<float>::max();
}

CPhysics::~CPhysics(void)
{

}

int CPhysics::GMCall(gmThread* a_thread, const std::string &name)
{
	if( name == "GetDepth" )
	{
		GM_CHECK_NUM_PARAMS(0);
		a_thread->PushFloat( m_depth );
		return GM_OK;
	}
	return GM_EXCEPTION;
}

void CPhysics::RegisterGM(gmMachine* pGM) {
	CObjectManager* pOM = GetGlobalObjectManager(*pGM);
	pOM->RegisterMemberFunction( CPhysics::COMPONENT_ID, "GetDepth" );
}

void CPhysics::Init(const ParameterList &params)
{
	//Check if size information exists
	if( params.find("width") != params.end() )
		m_width = boost::lexical_cast<float>(params.find("width")->second);
	if( params.find("height") != params.end() )
		m_height = boost::lexical_cast<float>(params.find("height")->second);
	if( params.find("depth") != params.end() )
		m_depth = boost::lexical_cast<float>(params.find("depth")->second);
}

void CPhysics::Message(MessageID message, const boost::any &arg)
{
	switch(message)
	{
	case Messages::Startup:
		{
			static const float m = std::numeric_limits<float>::max();

			//Check if a render component exists
			IComponent* pComp = GetObjectManager()->GetComponent(GetObjectID(), CRender::COMPONENT_ID);
			if( pComp )
			{
				CRender* pRender = dynamic_cast<CRender*>(pComp);
				
				//Get size information from texture
				if( m_width == m )
					m_width = pRender->GetImageInformation("width");
				if( m_height == m )
					m_height = pRender->GetImageInformation("height");
				if( m_depth == m )
					m_depth = pRender->GetImageInformation("depth");
			}

			//Default all things not initialized
			if( m_width == m ) m_width = 1.0f;
			if( m_height == m ) m_height = 0.0f;
			if( m_depth == m ) m_depth = 12.0f;
		}break;
	case Messages::TryMove:
		{
			//Get our new position
			bug::math::CVector* vMove = boost::any_cast<bug::math::CVector*>(arg);
			bug::math::CVector newPos = GetEntity()->Position()+*vMove;

			//Create our new rect
			Rect ourRect = CreateRect(this, newPos);

			//Iterate over all objects
			CArea::ObjectList::iterator iter = GetArea()->StartIterate();
			for(; iter != GetArea()->EndIterator(); iter++)
			{
				//Check for intersections
				IComponent *pComp = GetObjectManager()->GetComponent(*iter, CPhysics::COMPONENT_ID);
				if(!pComp) continue;

				//Get physics object
				CPhysics *pPhys = dynamic_cast<CPhysics*>(pComp);
				if(pPhys->GetObjectID() == GetObjectID()) continue;
				CEntity *pEnt = pComp->GetEntity();

				//Get position
				bug::math::CVector pos = pEnt->Position();

				//Create their rect
				Rect theirRect = CreateRect(pPhys, pos);

				//Check for intersection
				bool bHit = Intersect(ourRect, theirRect);
				if(bHit) {
					//Stop the move operation
					(*vMove) = bug::math::CVector();

					//Signal other component
					GetObjectManager()->Message(GetObjectID(), Messages::Collide, *iter);
					GetObjectManager()->Message(pPhys->GetObjectID(), Messages::Collide, GetObjectID());

					//Signal GM
					GetObjectManager()->CallHook(GetObjectID(), "OnCollide", gmVariable(GetObjectManager()->GetObjectTable(pPhys->GetObjectID())));
					GetObjectManager()->CallHook(pPhys->GetObjectID(), "OnCollide", gmVariable(GetObjectManager()->GetObjectTable(GetObjectID())));

					//Stops the loop
					//TODO: Find a better way
					break;
				}
			}
		}break;
	}
}

CPhysics::Rect CPhysics::CreateRect(CPhysics* pPhys, const bug::math::CVector &pos)
{
	Rect ourRect;
	ourRect.left = pos.m_x;
	ourRect.right = pos.m_x + pPhys->Width();
	ourRect.top = pos.m_z + pPhys->Height() - pPhys->Depth();
	ourRect.bottom = pos.m_z + pPhys->Height();
	return ourRect;
}

bool CPhysics::Intersect( const Rect& r1, const Rect& r2 )
{
	if( r1.bottom < r2.top ) return false;
	if( r1.top > r2.bottom ) return false;
	if( r1.right < r2.left ) return false;
	if( r1.left > r2.right ) return false;
	return true;
}